package cn.iocoder.yudao.module.trade.order.service;

import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.exception.Assertions;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.api.dine.DineConfigApi;
import cn.iocoder.yudao.module.api.dine.vo.DineConfigVO;
import cn.iocoder.yudao.module.api.infrastructure.config.ConfigApiV2;
import cn.iocoder.yudao.module.api.infrastructure.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.api.member.assets.MemberDigitalAssetsApi;
import cn.iocoder.yudao.module.api.member.assets.dto.MemberDigitalAssetsRespDTO;
import cn.iocoder.yudao.module.api.member.level.MemberLevelApi;
import cn.iocoder.yudao.module.api.member.level.dto.MemberLevelRespDTO;
import cn.iocoder.yudao.module.api.member.vip.VipUserApi;
import cn.iocoder.yudao.module.api.member.vip.enums.VipTypeEnum;
import cn.iocoder.yudao.module.api.product.sku.ProductSkuApi;
import cn.iocoder.yudao.module.api.product.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.api.product.spu.ProductSpuApi;
import cn.iocoder.yudao.module.api.product.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.api.promotion.activity.enums.PromotionTypeEnum;
import cn.iocoder.yudao.module.api.promotion.discount.DiscountActivityApi;
import cn.iocoder.yudao.module.api.promotion.discount.dto.DiscountProductRespDTO;
import cn.iocoder.yudao.module.api.promotion.groupon.GrouponPackageApi;
import cn.iocoder.yudao.module.api.promotion.groupon.dto.GrouponPackageRespDTO;
import cn.iocoder.yudao.module.api.promotion.reward.RewardActivityApi;
import cn.iocoder.yudao.module.api.promotion.reward.dto.RewardActivityMatchRespDTO;
import cn.iocoder.yudao.module.api.shop.info.ShopInfoApi;
import cn.iocoder.yudao.module.api.shop.info.dto.ShopInfoDTO;
import cn.iocoder.yudao.module.trade.order.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.order.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.order.service.calculator.TradeDiscountActivityAndMemberLevelPriceCalculator;
import cn.iocoder.yudao.module.trade.order.service.calculator.TradePriceCalculator;
import cn.iocoder.yudao.module.trade.order.service.calculator.TradePriceCalculatorHelper;
import cn.iocoder.yudao.module.trade.order.vo.AppTradeProductSettlementRespVO;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.api.infrastructure.ConfigConstants.CONFIG_MEMBER;
import static cn.iocoder.yudao.module.api.trade.ErrorCodeConstants.PRICE_CALCULATE_NOT_SUPPORTED_MULTI_SHOP;
import static cn.iocoder.yudao.module.api.trade.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;

/**
 * 价格计算 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
@Slf4j
public class TradePriceServiceImpl implements TradePriceService {

    @Resource
    private ShopInfoApi shopInfoApi;
    @Resource
    private ProductSkuApi productSkuApi;
    @Resource
    private ProductSpuApi productSpuApi;
    @Resource
    private DiscountActivityApi discountActivityApi;
    @Resource
    private RewardActivityApi rewardActivityApi;
    @Resource
    private GrouponPackageApi grouponPackageApi;
    @Resource
    private MemberLevelApi memberLevelApi;
    @Resource
    private VipUserApi vipUserApi;
    @Resource
    private MemberDigitalAssetsApi memberDigitalAssetsApi;
    @Resource
    private ConfigApiV2 configApiV2;
    @Resource
    private DineConfigApi dineConfigApi;

    @Resource
    private List<TradePriceCalculator> priceCalculators;
    @Resource
    private TradeDiscountActivityAndMemberLevelPriceCalculator discountActivityPriceCalculator;

    private MemberConfigRespDTO memberConfig;
    private final Lock lock = new ReentrantLock();

    @PostConstruct
    private void initMemberConfig() {
        memberConfig = configApiV2.getConfig(CONFIG_MEMBER, MemberConfigRespDTO.class);
        Assertions.notNull(memberConfig, "会员配置不存在");
    }

    @Override
    public TradePriceCalculateRespBO calculateOrderPrice(TradePriceCalculateReqBO calculateReqBO) {
        List<ProductSkuRespDTO> skuList = new ArrayList<>();
        List<ProductSpuRespDTO> spuList = new ArrayList<>();
        GrouponPackageRespDTO grouponPackage = null;
        // TODO 团购订单不应该杂糅进购物订单，后续考虑抽象出通用的交易订单及交易订单扩展，以便关联具体的业务订单数据
        boolean hasGrouponPackage = calculateReqBO.getGrouponPackageId() != null && calculateReqBO.getGrouponPackageId() > 0;
        if (hasGrouponPackage) {
            // 基于团购套餐下单无需校验产品信息
            grouponPackage = grouponPackageApi.getDetail(calculateReqBO.getGrouponPackageId());
            Assertions.notNull(grouponPackage, "未获取到团购套餐");
            Assertions.notEmpty(grouponPackage.getProducts(), "未获取到团购套餐产品");
            calculateReqBO.setShopId(grouponPackage.getShopId());
        } else {
            skuList = checkSkuList(calculateReqBO);
            Assertions.notEmpty(skuList, "未获取到产品规格信息");
            for (ProductSkuRespDTO sku : skuList) {
                sku.setPointDeductUnitPrice(memberConfig.getPointTradeDeductUnitPrice());
                sku.setCryptocurrencyDeductUnitPrice(memberConfig.getCryptocurrencyTradeDeductUnitPrice());
            }
            spuList = checkSpuList(skuList);
            Assertions.notEmpty(spuList, "未获取到产品信息");
            for (ProductSpuRespDTO spu : spuList) {
                spu.setPointDeductUnitPrice(memberConfig.getPointTradeDeductUnitPrice());
                spu.setCryptocurrencyDeductUnitPrice(memberConfig.getCryptocurrencyTradeDeductUnitPrice());
            }
            // 单店铺结算检查：从 SKU 中获取店铺编号数组，若含多店铺则抛出异常
            long shopCount = spuList.stream().map(ProductSpuRespDTO::getShopId).distinct().count();
            if (shopCount > 1) {
                throw exception(PRICE_CALCULATE_NOT_SUPPORTED_MULTI_SHOP);
            }
            Long shopId = spuList.get(0).getShopId();
            shopInfoApi.validShopInfo(shopId);
            calculateReqBO.setShopId(shopId);
        }
        try {
            lock.lock();
            DineConfigVO dineConfig = dineConfigApi.getDetail(calculateReqBO.getShopId());
            // 计算价格
            TradePriceCalculateRespBO calculateRespBO = TradePriceCalculatorHelper.buildCalculateResponse(calculateReqBO, spuList, skuList, grouponPackage, dineConfig);
            // 初始化用户配置、会员身份、积分、联盟豆等
            calculateRespBO.setMemberConfig(memberConfig);
            calculateRespBO.setValidVip(vipUserApi.validVip(calculateReqBO.getUserId(), null));
            calculateRespBO.setValidMerchantVip(vipUserApi.validVip(calculateReqBO.getUserId(), VipTypeEnum.MERCHANT));
            MemberDigitalAssetsRespDTO digitalAssets = memberDigitalAssetsApi.getDetail(calculateReqBO.getUserId());
            calculateRespBO.setTotalPoint(digitalAssets.getPoint());
            calculateRespBO.setTotalCryptocurrency(digitalAssets.getCryptocurrency());
            // 批量计算各种场景下的价格
            for (TradePriceCalculator calculator : priceCalculators) {
                calculator.calculate(calculateReqBO, calculateRespBO);
            }
            if (calculateRespBO.getPrice().getPayPrice() <= 0) {
                // 如果最终支付金额小于等于 0，则抛出业务异常（积分兑换订单及联盟豆全额抵扣例外）
                boolean isPointActivity = calculateReqBO.getPointActivityId() != null && calculateReqBO.getPointActivityId() > 0;
                if (isPointActivity || calculateReqBO.isUseCryptocurrencyDeduction()) {
                    return calculateRespBO;
                }
                log.error("[calculateOrderPrice][价格计算不正确，请求 calculateReqDTO({})，结果 priceCalculate({})]",
                        calculateReqBO, calculateRespBO.getPrice());
                throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
            }
            return calculateRespBO;
        } finally {
            lock.unlock();
        }
    }

    private List<ProductSkuRespDTO> checkSkuList(TradePriceCalculateReqBO reqBO) {
        if (CollUtil.isEmpty(reqBO.getItems())) {
            return new ArrayList<>();
        }
        // 获得产品 SKU 数组
        Map<Long, Integer> skuIdCountMap = convertMap(reqBO.getItems(),
                TradePriceCalculateReqBO.Item::getSkuId, TradePriceCalculateReqBO.Item::getCount);
        return productSkuApi.validateSkus(skuIdCountMap);
    }

    private List<ProductSpuRespDTO> checkSpuList(List<ProductSkuRespDTO> skuList) {
        if (CollUtil.isEmpty(skuList)) {
            return new ArrayList<>();
        }
        return productSpuApi.validateSpuList(convertSet(skuList, ProductSkuRespDTO::getSpuId));
    }

    @Override
    public List<AppTradeProductSettlementRespVO> calculateProductPrice(Long userId, List<Long> spuIds) {
        // 1.1 获得 SPU 与 SKU 的映射
        List<ProductSkuRespDTO> allSkuList = productSkuApi.getSkuListBySpuId(spuIds);
        Map<Long, List<ProductSkuRespDTO>> spuIdAndSkuListMap = convertMultiMap(allSkuList, ProductSkuRespDTO::getSpuId);
        // 1.3 获得限时折扣活动
        Map<Long, DiscountProductRespDTO> skuIdAndDiscountMap = convertMap(
                discountActivityApi.getMatchDiscountProductListBySkuIds(convertSet(allSkuList, ProductSkuRespDTO::getId)),
                DiscountProductRespDTO::getSkuId);
        // 1.4 获得满减送活动
        List<RewardActivityMatchRespDTO> rewardActivityMap = rewardActivityApi.getMatchRewardActivityListBySpuIds(spuIds);

        // 2. 价格计算
        List<AppTradeProductSettlementRespVO> respVOS = convertList(spuIds, spuId -> {
            AppTradeProductSettlementRespVO respVO = new AppTradeProductSettlementRespVO();
            respVO.setSpuId(spuId);
            // 2.1 优惠价格
            List<ProductSkuRespDTO> skuList = spuIdAndSkuListMap.get(spuId);
            List<AppTradeProductSettlementRespVO.Sku> skuVOList = convertList(skuList, sku -> {
                AppTradeProductSettlementRespVO.Sku respVOSku = new AppTradeProductSettlementRespVO.Sku();
                respVOSku.setId(sku.getId());
                TradePriceCalculateRespBO.OrderItem orderItem = new TradePriceCalculateRespBO.OrderItem();
                orderItem.setPayPrice(sku.getPrice());
                orderItem.setCount(1);
                // 计算限时折扣的优惠价格
                DiscountProductRespDTO discountProduct = skuIdAndDiscountMap.get(sku.getId());
                Integer discountPrice = discountActivityPriceCalculator.calculateActivityPrice(discountProduct, orderItem);
                // 计算 VIP 优惠金额
                MemberLevelRespDTO level = memberLevelApi.getMemberLevelByUserId(userId);
                Integer vipPrice = discountActivityPriceCalculator.calculateMemberLevelPrice(level, orderItem);
                if (discountPrice <= 0 && vipPrice <= 0) {
                    return respVOSku;
                }
                // 选择一个大的优惠
                if (discountPrice > vipPrice) {
                    respVOSku.setPromotionPrice(sku.getPrice() - discountPrice);
                    respVOSku.setPromotionType(PromotionTypeEnum.DISCOUNT_ACTIVITY.getValue());
                    respVOSku.setPromotionId(discountProduct.getId());
                    respVOSku.setPromotionEndTime(discountProduct.getActivityEndTime());
                    return respVOSku;
                } else {
                    respVOSku.setPromotionPrice(sku.getPrice() - vipPrice);
                    respVOSku.setPromotionType(PromotionTypeEnum.MEMBER_LEVEL.getValue());
                    return respVOSku;
                }
            });
            respVO.setSkus(skuVOList);
            // 2.2 满减送活动
            RewardActivityMatchRespDTO rewardActivity = CollUtil.findOne(rewardActivityMap,
                    activity -> CollUtil.contains(activity.getSpuIds(), spuId));
            respVO.setRewardActivity(BeanUtils.toBean(rewardActivity, AppTradeProductSettlementRespVO.RewardActivity.class));
            return respVO;
        });
        // 3. 获得到店自取的店铺信息（外卖产品）
        Set<Long> shopIds = respVOS.stream().map(AppTradeProductSettlementRespVO::getShopId).collect(Collectors.toSet());
        Map<Long, ShopInfoDTO> shopInfoMap = shopInfoApi.getShopInfoMap(shopIds);
        respVOS.forEach(vo -> vo.setShop(shopInfoMap.get(vo.getShopId())));
        return respVOS;
    }

}
